#include "Galaxy.hh"
#include "User.hh"
#include "Player.hh"
#include "../Exception.hh"
#include <algorithm>
#include <boost/nondet_random.hpp>
#include <boost/random.hpp>

using namespace boost;
using namespace Geom;
using namespace World;
using namespace std;

/// \brief Globális zárolásra szolgáló mutex.
QMutex Galaxy::staticLock(QMutex::Recursive);

/// \brief Az összes galaxis.
Galaxy::GalaxyMap Galaxy::galaxies;

/**
 * \brief Új galaxis létrehozása.
 * \param name       Az új galaxis neve. Egyedinek kell lennie.
 * \param user       A létrehozó felhasználó.
 * \param player     A létrehozó felhasználó új játékosa. Kimeneti paraméter.
 * \param minPlayers A játékosok minimális száma, amivel a játék elkezdhető.
 *                   Opcionális, alapértelmezés: 2
 * \param maxPlayers A játékosok maximális száma, ahányan beléphetnek.
 *                   Opcionális, alapértelmezés: korlátlan.
 * \return Az új galaxis.
 * \throw GalaxyExistsException Már létezik a megadott nevű galaxis.
 */
spGalaxy Galaxy::construct(
  QString const& name, spUser user, spPlayer& player,
  u2 minPlayers, u2 maxPlayers
) {
    QMutexLocker locker(&staticLock);

    if (galaxies.count(name))
        throw GalaxyExistsException(name);

    random_device rng("/dev/urandom");
    uniform_real< f8 > range(-GALAXY_SIZE, +GALAXY_SIZE);
    variate_generator< random_device&, uniform_real< f8 > > random(rng, range);

    spGalaxy galaxy(new Galaxy(name, minPlayers, maxPlayers));
    player = Player::construct(galaxy, user);
    galaxy->players.insert(user.data(), player);
    player->add_fleet(Vector<>(random(), random()), 1, 4, 0, 1, 1);
    u4 avgPlayers =
      (maxPlayers == UNLIMITED) ?
        (2 * minPlayers) :
        ((minPlayers + maxPlayers) / 2);

    for (u4 i = 0; i < 16 * avgPlayers; ++i) {
        galaxy->stars.insert(i, Star::construct(i, galaxy));
    }

    galaxies.insert(name, galaxy);
    return galaxy;
}

/**
 * \brief Galaxis törlése.
 * \param name A törlendő galaxis neve.
 */
void Galaxy::destruct(QString const& name) {
    QMutexLocker locker(&staticLock);

    if (!galaxies.count(name))
        throw GalaxyNotFoundException(name);

    galaxies.remove(name);
}

/**
 * \brief Galaxis keresése.
 * \param name A keresett galaxis neve.
 * \return A keresett galaxis.
 * \throw GalaxyNotFoundException Nincs ilyen nevű galaxis.
 */
spGalaxy Galaxy::select(QString const& name) {
    QMutexLocker locker(&staticLock);
    GalaxyMap::iterator iter = galaxies.find(name);

    if (iter == galaxies.end())
        throw GalaxyNotFoundException(name);

    return *iter;
}

/// \brief Összes galaxis lekérdezése.
Galaxy::GalaxyMap Galaxy::get_galaxies() {
    QMutexLocker locker(&staticLock);
    GalaxyMap result = galaxies;
    return result;
}

/**
 * \brief Galaxis konstruktor.
 * \param name         Egyedi név.
 * \param minPlayers A játékosok minimális száma.
 * \param maxPlayers A játékosok maximális száma.
 */
Galaxy::Galaxy(QString const& name, u2 minPlayers, u2 maxPlayers):
    noncopyable(),
    lock(QMutex::Recursive),
    name(name),
    started(false),
    minPlayers(minPlayers),
    maxPlayers(maxPlayers),
    nextTurnCtr(),
    players(),
    stars() {}

/// \brief Galaxis törlése
void Galaxy::destruct() {
    Galaxy::destruct(name);
}

/**
 * \brief Egy felhasználó csatlakozik a galaxishoz.
 * \param user   A felhasználó.
 * \param player Az új játékos. Kimeneti paraméter.
 * \param others A többi játékos. Kimeneti paraméter.
 * \return Létrejött-e új játékos.
 * \throw GameStartedException A játék már folyamatban van.
 *
 * Két esetben használható: mikor egy felhasználó egy új galaxisba lép be, vagy
 * mikor egy felhasználó ismét belép egy játékba, amibe korábban már belépett. A
 * második esetben nem jön létre új játékos.
 *
 * Figyelmeztetés! SOHA nem szabad a this-ből QSharedPointer-t csinálni.
 */
bool Galaxy::join(spUser user, spPlayer& player, PlayerMapByUser& others) {
    QMutexLocker locker(&lock);

    if (maxPlayers != UNLIMITED && players.size() >= maxPlayers)
        throw GalaxyFullException();

    others = players;
    PlayerMapByUser::iterator iter = others.find(user.data());

    if (iter != others.end()) {
        player = iter.value();
        others.erase(iter);
        return false;
    }

    if (started)
        throw GameStartedException();
    random_device rng("/dev/urandom");
    uniform_real< f8 > range(-64, +64);
    variate_generator< random_device&, uniform_real< f8 > > random(rng, range);
    // Azért van select(name) mert csak így lehet QSharedPointert kapni.
    spGalaxy self = select(name);
    player = Player::construct(self, user);
    players.insert(user.data(), player);
    player->add_fleet(Vector<>(random(), random()), 1, 4, 0, 1, 1);
    return true;
}

/**
 * \brief Idő léptetése.
 *
 * Minden benne lévő objektum idejét lépteti a next_turn() hívásával.
 */
void Galaxy::next_turn() {
    QMutexLocker locker(&lock);
    started = true;
    nextTurnCtr = 0;

    for (
      PlayerMap::iterator player = players.begin();
      player != players.end();
      ++player
    ) {
        (*player)->next_turn();
    }

    for (
      StarMap::iterator star = stars.begin();
      star != stars.end();
      ++star
    ) {
        (*star)->next_turn();
    }
}

/**
 * \brief Egy játékos befejezte a körét.
 * \return Telt-e az idő (minden játékos befejezte-e a körét).
 *
 * Meghívja a next_turn()-t, ha minden játékos befejezte a körét, és elegendő
 * játékos van.
 */
bool Galaxy::inc_next_turn_ctr() {
    QMutexLocker locker(&lock);
    u2 const n = players.size();

    if (++nextTurnCtr >= n && n >= minPlayers) {
        next_turn();
        return true;
    }

    return false;
}

/**
 * \brief Játékos lekérdezése.
 * \param id A játékoshoz tartozó felhasználó.
 * \return A játékos.
 */
spPlayer Galaxy::get_player(PlayerMapByUser::key_type id) const {
    QMutexLocker locker(&lock);
    return players.value(id);
}

SYNC_GETTER(Galaxy, PlayerMapByUser, players)

/**
 * \brief Flotta lekérdezése.
 * \param id Azonosító.
 * \return A keresett flotta.
 */
spFleet Galaxy::get_fleet(FleetMap::key_type id) const {
    return get_fleets().value(id);
}

/**
 * \brief Összes flotta lekérdezése.
 */
Galaxy::FleetMap Galaxy::get_fleets() const {
    QMutexLocker locker(&lock);
    FleetMap result;

    for (
      PlayerMapByUser::const_iterator player = players.begin();
      player != players.end();
      ++player
    ) {
        result.unite((*player)->get_fleets());
    }

    return result;
}

/**
 * \brief Csillag lekérdezése.
 * \param id Azonosító.
 * \return A keresett csillag.
 */
spStar Galaxy::get_star(u8 id) const {
    QMutexLocker locker(&lock);
    spStar result = stars.value(id);
    return result;
}

SYNC_GETTER(Galaxy, StarMap, stars)
